package org.apache.rocketmq.client.java.message;



import org.apache.rocketmq.client.java.clientapis.message.MessageId;
import org.apache.rocketmq.client.java.misc.Utilities;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Calendar;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;



/**
 * The codec for the message-id.
 *
 * <p>Codec here provides the following two functions:
 * 1. Provide decoding function of message-id of all versions above v0.
 * 2. Provide a generator of message-id of v1 version.
 *
 * <p>The message-id of versions above V1 consists of 17 bytes in total. The first two bytes represent the version
 * number. For V1, these two bytes are 0x0001.
 *
 * <h3>V1 message id example</h3>
 *
 * <pre>
 * ┌──┬────────────┬────┬────────┬────────┐
 * │01│56F7E71C361B│21BC│024CCDBE│00000000│
 * └──┴────────────┴────┴────────┴────────┘
 * </pre>
 *
 * <h3>V1 version message id generation rules</h3>
 *
 * <pre>
 *                     process id(lower 2bytes)
 *                             ▲
 * mac address(lower 6bytes)   │   sequence number(big endian)
 *                    ▲        │          ▲ (4bytes)
 *                    │        │          │
 *              ┌─────┴─────┐ ┌┴┐ ┌───┐ ┌─┴─┐
 *       0x01+  │     6     │ │2│ │ 4 │ │ 4 │
 *              └───────────┘ └─┘ └─┬─┘ └───┘
 *                                  │
 *                                  ▼
 *           seconds since 2021-01-01 00:00:00(UTC+0)
 *                         (lower 4bytes)
 * </pre>
 */
/**
 * @课程描述:从零带你写框架系列中的课程，整个系列包含netty，xxl-job，rocketmq，nacos，sofajraft，spring，springboot，disruptor，编译器，虚拟机等等。
 * @author：陈清风扬，个人微信号：chenqingfengyangjj。
 * @date:2025/6/16
 * @方法描述：消息Id编解码器
 */
public class MessageIdCodec {

    //消息Id长度，适用于V1或者更高版本，这里的长度指的是消息Id字符串长度，是34字符
    public static final int MESSAGE_ID_LENGTH_FOR_V1_OR_LATER = 34;

    //V0版本的消息Id的起始标志"00"
    public static final String MESSAGE_ID_VERSION_V0 = "00";

    //V1版本的消息Id的起始标志"01"，完整格式为"01" + "MAC地址" + "进程Id信息" + "时间部分" + "序号部分"
    public static final String MESSAGE_ID_VERSION_V1 = "01";

    //单例模式
    private static final MessageIdCodec INSTANCE = new MessageIdCodec();

    //V1版本消息Id中的进程固定部分字符串，包含MAC地址和进程Id信息
    private final String processFixedStringV1;

    //自定义纪元时间到现在为止的秒数
    private final long secondsSinceCustomEpoch;

    //系统启动的时间
    private final long secondsStartTimestamp;

    //系统当前时间到自定义纪元时间的差值
    private long seconds;

    //序号生成器
    private final AtomicInteger sequence;

    //构造方法
    private MessageIdCodec() {
        //创建一个8字节的ByteBuffer，用于构建进程固定部分
        //所谓固定部分就是MAC地址和进程Id信息，其中6字节存储MAC地址，2字节存储进程Id
        ByteBuffer buffer = ByteBuffer.allocate(8);
        //设置字节序为大端序
        buffer.order(ByteOrder.BIG_ENDIAN);

        //得到当前机器的MAC地址作为前缀
        //MAC地址用于区分不同的物理机器，提高Id的唯一性
        byte[] prefix0 = Utilities.macAddress();
        //将MAC地址的前6个字节放入buffer
        buffer.put(prefix0, 0, 6);

        //创建一个4字节的ByteBuffer，用于存储进程Id
        ByteBuffer pidBuffer = ByteBuffer.allocate(4);
        //设置字节序为大端序
        pidBuffer.order(ByteOrder.BIG_ENDIAN);
        //得到当前程序进程Id
        final int pid = Utilities.processId();
        //将进程Id放入pidBuffer中
        pidBuffer.putInt(pid);
        //从pidBuffer中取出后2个字节放入之前创建的容量为8字节的buffer
        //这里只使用进程ID的低16位，因为通常这部分足够区分不同进程
        buffer.put(pidBuffer.array(), 2, 2);

        //将buffer从写模式切换到读模式
        buffer.flip();

        //给processFixedStringV1成员变量赋值，包含了MAC地址和进程Id信息，用于填充消息Id的固定部分
        //转换为十六进制字符串，一共是16字符
        processFixedStringV1 = Utilities.encodeHexString(buffer, false);
        //得到自定义纪元时间以来的秒数
        secondsSinceCustomEpoch = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis() - customEpochMillis());
        //得到当前系统的启动时间
        secondsStartTimestamp = TimeUnit.NANOSECONDS.toSeconds(System.nanoTime());
        //得到系统当前时间到自定义纪元时间的差值
        seconds = deltaSeconds();
        //创建序号生成器，起始值为0
        sequence = new AtomicInteger(0);
    }

    public static MessageIdCodec getInstance() {
        return INSTANCE;
    }


    /**
     * @课程描述:从零带你写框架系列中的课程，整个系列包含netty，xxl-job，rocketmq，nacos，sofajraft，spring，springboot，disruptor，编译器，虚拟机等等。
     * @author：陈清风扬，个人微信号：chenqingfengyangjj。
     * @方法描述：自定义纪元时间
     */
    private long customEpochMillis() {
        //设置2021-01-01 00:00:00作为基准时间点
        final Calendar calendar = Calendar.getInstance();
        calendar.set(Calendar.YEAR, 2021);
        calendar.set(Calendar.MONTH, Calendar.JANUARY);
        calendar.set(Calendar.DAY_OF_MONTH, 1);
        calendar.set(Calendar.HOUR_OF_DAY, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        calendar.set(Calendar.MILLISECOND, 0);
        return calendar.getTime().getTime();
    }


    /**
     * @课程描述:从零带你写框架系列中的课程，整个系列包含netty，xxl-job，rocketmq，nacos，sofajraft，spring，springboot，disruptor，编译器，虚拟机等等。
     * @author：陈清风扬，个人微信号：chenqingfengyangjj。
     * @方法描述：得到系统当前时间到自定义纪元时间的差值的方法
     */
    private long deltaSeconds() {
        return TimeUnit.NANOSECONDS.toSeconds(System.nanoTime()) - secondsStartTimestamp + secondsSinceCustomEpoch;
    }


    /**
     * @课程描述:从零带你写框架系列中的课程，整个系列包含netty，xxl-job，rocketmq，nacos，sofajraft，spring，springboot，disruptor，编译器，虚拟机等等。
     * @author：陈清风扬，个人微信号：chenqingfengyangjj。
     * @方法描述：生成下一个消息Id的方法
     */
    public MessageId nextMessageId() {
        //创建一个8字节的ByteBuffer，用于构建消息Id的时间和序号部分
        ByteBuffer buffer = ByteBuffer.allocate(8);
        //设置字节序为大端序
        buffer.order(ByteOrder.BIG_ENDIAN);

        //创建一个8字节的ByteBuffer，用于存储时间差值
        final ByteBuffer deltaSecondsBuffer = ByteBuffer.allocate(8);
        //设置字节序为大端序
        deltaSecondsBuffer.order(ByteOrder.BIG_ENDIAN);
        //计算系统当前时间到自定义纪元时间的差值的方法
        final long deltaSeconds = deltaSeconds();
        //如果时间差值有变化，则更新seconds成员变量
        if (seconds != deltaSeconds) {
            seconds = deltaSeconds;
        }
        //将时间差值放入deltaSecondsBuffer中
        deltaSecondsBuffer.putLong(seconds);
        //从deltaSecondsBuffer中取出后4个字节放入刚才创建的buffer中，现在消息Id中的时间部分就填充完毕了
        buffer.put(deltaSecondsBuffer.array(), 4, 4);
        //得到唯一序号
        buffer.putInt(sequence.getAndIncrement());
        //将buffer从写模式切换到读模式
        buffer.flip();
        //得到消息Id的后缀部分
        //转换为十六进制字符串，一共是16字符，16字符加上processFixedStringV1字符就是32字符
        final String suffix = processFixedStringV1 + Utilities.encodeHexString(buffer, false);
        //创建并返回一个MessageIdImpl消息Id对象
        //MESSAGE_ID_VERSION_V1是2个字符，加起来一共34字符
        return new MessageIdImpl(MESSAGE_ID_VERSION_V1, suffix);
    }


    /**
     * @课程描述:从零带你写框架系列中的课程，整个系列包含netty，xxl-job，rocketmq，nacos，sofajraft，spring，springboot，disruptor，编译器，虚拟机等等。
     * @author：陈清风扬，个人微信号：chenqingfengyangjj。
     * @方法描述：解码消息Id字符串的方法
     */
    public MessageId decode(String messageId) {
        //判断字符串长度是否为34
        if (MESSAGE_ID_LENGTH_FOR_V1_OR_LATER != messageId.length()) {
            //如果不是则返回一个V0版本的MessageIdImpl消息Id对象
            return new MessageIdImpl(MESSAGE_ID_VERSION_V0, messageId);
        }//如果是则返回一个V1版本的MessageIdImpl消息Id对象
        return new MessageIdImpl(messageId.substring(0, 2), messageId.substring(2));
    }
}
